package sketch.ounit.fuzz;

import randoop.ComponentManager;
import randoop.ExecutableSequence;
import randoop.ExecutionOutcome;
import randoop.NormalExecution;
import randoop.Sequence;
import randoop.Variable;
import randoop.util.Reflection;
import randoop.util.SimpleList;
import sketch.util.Checker;
import sketch.util.Randomness;
import sketch.util.RandoopUtil;

/**
 * This class just picks an assembled sequence from the generated sequence pool.
 * */
public class AssembledSequenceObjectCreation<T>  implements AssembledSequence<T> {

	public final Class<T> clazz;
	public final ExecutableSequence eSeq;
	
	protected int offset = 0;
	
	protected final ComponentManager components;
	
	protected int result_index = -1;
	
	public AssembledSequenceObjectCreation(Class<T> clazz, ComponentManager components) {
		Checker.checkNull(clazz, "The clazz could not be null.");
		Checker.checkNull(components, "The components could not be null.");
		this.components = components;
		this.clazz = clazz;
		this.eSeq = this.getExecutableSequenceForType(clazz);
		//execute it
		//this.eSeq.execute(null);
	}
	
	protected ExecutableSequence getExecutableSequenceForType(Class<?> clz) {
		//a list of active sequences
		SimpleList<Sequence> sequences = components.getSequencesForType(clz, false);
		if(sequences.isEmpty()) {
			return null;
		} else {
			Sequence sequence = Randomness.randomMember(sequences.toJDKList());
			return new ExecutableSequence(sequence);
		}
	}
	
	@Override
	public int size() {
		if(!this.isValidSequence()) {
			return 0;
		}
		return eSeq.sequence.size();
	}
	
	//this can throw exception, need to fix
	@Override
	public T assemble_and_execute_sequence() {
		//there is no created sequence, just returns null
		if(!this.isValidSequence()) {
			return null;
		}
		this.eSeq.execute(null);
		Checker.checkTrue(!this.eSeq.hasNonExecutedStatements(), "The statement should not have un-executed statements.");
		ExecutionOutcome[] outcomes = this.eSeq.getAllResults();
		Object retValue = null;
		boolean found = false;
		for(int i = outcomes.length - 1; i > -1; i--) {
			ExecutionOutcome outcome = outcomes[i];
			Checker.checkTrue(outcome instanceof NormalExecution, "The outcome should be normal execution");
			retValue = ((NormalExecution)outcome).getRuntimeValue();
			Class<?> valueClass = this.eSeq.sequence.statements.get(i).statement.getOutputType();
			if(Reflection.canBeUsedAs(valueClass, this.clazz)) {
				result_index = i;
				found = true;
				break;
			}
		}
		Checker.checkTrue(found, "The result should be found!");
		//possibly been null
		return (T) retValue;
	}
	
	public boolean isValidSequence() {
		return this.eSeq != null;
	}
	
	protected void checkSequenceValidity() {
		//do nothing in this class
		Checker.checkNull(this.eSeq, "The eSeq of this class could not be null.");
	}
	
	@Override
	public String toCodeString(int offset, String receiver, String[] argnames, String[] expressionNames) {
		this.checkSequenceValidity();
		Checker.checkTrue(offset > -1, "The offset value should >= 0.");
		StringBuilder sb = new StringBuilder();
		Sequence sequence = this.eSeq.sequence;
		RandoopUtil.appendSequenceWithOffset(sequence, offset, sb);
		return sb.toString();
	}

	@Override
	public String getResultVar(int offset) {
		this.checkSequenceValidity();
		Checker.checkTrue(this.result_index != -1, "You need to first execute the sequence.");
		Variable v = this.eSeq.sequence.getVariable(this.result_index);
		Variable varOffset = new Variable(v.sequence, v.index + offset);
		return varOffset.getName();
	}

	public void setOutputOffset(int offset) {
		Checker.checkTrue(offset >= 0, "The value of offset is illegal: " + offset);
		this.offset = offset;
	}
	
	@Override
	public String toString() {
		return this.toCodeString();
	}
	
	public String toCodeString() {
		return this.toCodeString(this.offset);
	}
	
	protected String toCodeString(int offset) {
		this.checkSequenceValidity();
		StringBuilder sb = new StringBuilder();
		Sequence sequence = this.eSeq.sequence;
		sequence.toCodeString();
		for(int i = 0; i < sequence.size(); i++) {
			RandoopUtil.appendSequenceStatementWithOffset(offset, sequence, sb, i);
		}
		
		return sb.toString();
	}
	
	@Override
	public int hashCode() {
		int hashcode = 0;
		if(this.isValidSequence()) {
			hashcode = this.eSeq.hashCode();
		}
		return hashcode + this.clazz.hashCode();
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof AssembledSequenceObjectCreation)) {
			return false;
		}
		AssembledSequenceObjectCreation seq = (AssembledSequenceObjectCreation)obj;
		if(!seq.isValidSequence() || !this.isValidSequence()) {
			return false;
		}
		return seq.clazz.equals(this.clazz) && seq.eSeq.equals(this.eSeq);
	}
}